import {ifArray, ifObject, nextArr} from "./utils.utils";

function Store(option) {
    //必要参数的默认值处理
    const {
        state = {},
        openPart = false,
    } = option

    //状态初始化
    this.$state = {};
    if (ifObject(state)) {
        this.$state = Object.assign({}, nextArr(state));
    }
    //页面+组件树
    this.$r = [];
    //创建时，添加组件
    const _create = function(r, o = {}) {
        r.$store = {}
        const {
            useProp
        } = o;
        if (o.hasOwnProperty('useProp')) {
            if (useProp && typeof useProp === 'string' || ifArray(useProp)) {
                r.$store.useProp = [].concat(useProp)
            } else {
                r.$store.useProp = []
            }
        }

        r.$store.useStore = canUseStore(o);
        r.$store.setState = function(obj, fn) {
            _store.setState(obj, fn)
        }
        r.$store.getState = function() {
            return nextArr(_store.$state)
        }
        if (canUseStore(o)) {
            _store.$r.push(r)
            if (r.$store.useProp) {
                r.setData({
                    $state: _filterKey(_store.$state, r.$store.useProp, (key, usekey) => key === usekey)
                })
            } else {
                r.setData({
                    $state: _store.$state
                })
            }
        }

    }
    //销毁时，移除组件
    const _destroy = function(r) {
        let index = _store.$r.findIndex(item => item === r);
        if (index > -1) {
            _store.$r.splice(index, 1);
        }
        // 在页面和组件销毁/移除时，清除定时器
        if (r.data.timer) {
            clearTimeout(r.data.timer);
            clearInterval(r.data.timer);
        }
        if (r.data.timers) {
            r.data.timers.forEach(timer => {
                clearTimeout(timer);
                clearInterval(timer);
            })
        }
    }
    //其他参数
    const _store = this;
    const canUseStore = function(o = {}) {
        return (openPart === true && o.useStore === true) || !openPart
    }

    const originPage = Page,
        originComponent = Component

    //重写Page
    App.Page = function(o = {}, ...args) {
        if (canUseStore(o)) {
            //状态注入
            o.data = Object.assign(o.data || {}, {
                $state: _store.$state
            })
        }
        //覆盖原周期
        const originCreate = o.onLoad
        o.onLoad = function() {
            _create(this, o)
            originCreate && originCreate.apply(this, arguments)
        }
        const originonDestroy = o.onUnload
        o.onUnload = function() {
            _destroy(this)
            originonDestroy && originonDestroy.apply(this, arguments)
        }
        originPage(o, ...args)
    }

    try {
        Page = App.Page
    } catch (e) {}

    //重写组件
    App.Component = function(o = {}, ...args) {
        //状态注入
        if (canUseStore(o)) {
            o.data = Object.assign(o.data || {}, {
                $state: _store.$state
            })
        }

        const {
            lifetimes = {}
        } = o

        let originCreate = lifetimes.attached || o.attached,
            originonDestroy = lifetimes.detached || o.detached
        const attached = function() {
            _create(this, o)
            originCreate && originCreate.apply(this, arguments)
        }

        const detached = function() {
            _destroy(this)
            originonDestroy && originonDestroy.apply(this, arguments)
        }
        if (ifObject(o.lifetimes)) {
            o.lifetimes.attached = attached
            o.lifetimes.detached = detached
        } else {
            o.attached = attached
            o.detached = detached
        }

        //覆盖原周期

        originComponent(o, ...args)
    }
    try {
        Component = App.Component
    } catch (e) {}
}

Store.prototype.setState = function(obj, fn = () => {}) {
    if (!ifObject(obj)) {
        throw new Error('setState的第一个参数须为object!');
    }
    console.time && console.time('setState');
    let prev = this.$state;
    let current = setData(obj, prev);
    this.$state = current;
    //如果有组件
    if (this.$r.length > 0) {
        let diffObj = diff(current, prev)
        console.log('diff后实际设置的值：', nextArr(diffObj))
        let keys = Object.keys(diffObj)
        if (keys.length > 0) {
            const newObj = {}
            keys.forEach(key => {
                newObj['$state.' + key] = diffObj[key]
            })
            let pros = this.$r.map(r => {
                if (r.$store.hasOwnProperty('useProp')) {
                    let useprops = _filterKey(
                        newObj,
                        r.$store.useProp,
                        (key, useKey) => (key === '$state.' + useKey) || !!key.match(new RegExp('^[\$]state.' + useKey + '[\.|\[]', 'g'))
                    );
                    if (Object.keys(useprops).length > 0) {
                        return new Promise(resolve => {
                            r.setData(useprops, resolve);
                        })
                    } else {
                        return Promise.resolve();
                    }
                }
                return new Promise(resolve => {
                    r.setData(newObj, resolve);
                })
            })
            Promise.all(pros).then(fn);
        } else {
            fn()
        }
    } else {
        fn()
    }
    console.timeEnd && console.timeEnd('setState')
}

Store.prototype.getState = function(){
    return nextArr(this.$state);
}

const _filterKey = function(obj, useKeys = [], fn) {
    let result = {}
    Object.keys(obj)
        .filter(key =>
            useKeys.some(usekey => {
                return fn(key, usekey)
            })
        )
        .forEach(key => {
            result[key] = obj[key]
        })
    return result
}

const setData = function(obj, data) {
    let result = nextArr(data)
    let origin = nextArr(obj)
    Object.keys(origin).forEach(key => {
        dataHandler(key, origin[key], result)
    })
    return result
}

const dataHandler = function(key, result, data) {
    let arr = pathHandler(key)
    let d = data
    for (let i = 0; i < arr.length - 1; i++) {
        keyToData(arr[i], arr[i + 1], d)
        d = d[arr[i]]
    }
    d[arr[arr.length - 1]] = result
}

const pathHandler = function(key) {
    let current = '',
        keyArr = []
    for (let i = 0, len = key.length; i < len; i++) {
        if (key[0] === '[') {
            throw new Error('key值不能以[]开头')
        }
        if (key[i].match(/\.|\[/g)) {
            cleanAndPush(current, keyArr)
            current = ''
        }
        current += key[i]
    }
    cleanAndPush(current, keyArr)
    return keyArr
}

const cleanAndPush = function(key, arr) {
    let r = cleanKey(key)
    if (r !== '') {
        arr.push(r)
    }
}

const keyToData = function(prev, current, data) {
    if (prev === '') {
        return
    }
    if (typeof current === 'number' && !ifArray(data[prev])) {
        data[prev] = []
    } else if (typeof current === 'string' && !ifObject(data[prev])) {
        data[prev] = {}
    }
}

const cleanKey = function(key) {
    if (key.match(/\[\S+\]/g)) {
        let result = key.replace(/\[|\]/g, '')
        if (!Number.isNaN(parseInt(result))) {
            return +result
        } else {
            throw new Error(`[]中必须为数字`)
        }
    }
    return key.replace(/\[|\.|\]| /g, '')
}

/**
 * diff算法
 * @author 逍遥
 */
const addDiff = function addDiff(
    current = {},
    prev = {},
    root = '',
    result = {}
) {
    Object.entries(current).forEach(item => {
        let key = item[0],
            value = item[1],
            path = root === '' ? key : root + '.' + key
        if (ifArray(current)) {
            path = root === '' ? key : root + '[' + key + ']'
        }

        if (!prev.hasOwnProperty(key)) {
            result[path] = value
        } else if (
            (ifObject(prev[key]) && ifObject(current[key])) ||
            (ifArray(prev[key]) && ifArray(current[key]))
        ) {
            addDiff(current[key], prev[key], path, result)
        } else if (prev[key] !== current[key]) {
            result[path] = value
        }
    })
    return result
}

const nullDiff = function nullDiff(
    current = {},
    prev = {},
    root = '',
    result = {}
) {
    Object.entries(prev).forEach(item => {
        let key = item[0],
            value = item[1],
            path = root === '' ? key : root + '.' + key
        if (ifArray(current)) {
            path = root === '' ? key : root + '[' + key + ']'
        }

        if (!current.hasOwnProperty(key)) {
            result[path] = null
        } else if (
            (ifObject(prev[key]) && ifObject(current[key])) ||
            (ifArray(prev[key]) && ifArray(current[key]))
        ) {
            nullDiff(current[key], prev[key], path, result)
        }
    })
    return result
}

const diff = function diff(current = {}, prev = {}) {
    let result = {}
    addDiff(current, prev, '', result)
    nullDiff(current, prev, '', result)
    return result
}

module.exports = Store;